#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试 RAG 管道在“内存向量为空”场景下：
1) 自动从 Chroma 恢复向量到内存向量库
2) 仍不足以满足 limit 时，触发 Chroma 回退检索
"""

import sys
from pathlib import Path
import pytest

# 将项目根目录加入 sys.path，保证 from src.* 导入可用
project_root = Path(__file__).resolve().parent.parent
if str(project_root) not in sys.path:
    sys.path.insert(0, str(project_root))

from src.core.knowledge_processor import KnowledgeProcessor
from src.database.manager import DatabaseManager
from src.utils.vectorizer import InMemoryVectorStore, DocumentVectorizer


class StubEmbeddingModel:
    name = "stub-embedder"

    async def embed_text(self, text: str):
        # 简单确定性向量
        base = sum(ord(c) for c in text) % 10
        return [base / 10.0, (base + 1) / 10.0, (base + 2) / 10.0]

    async def embed_texts(self, texts):
        return [await self.embed_text(t) for t in texts]


@pytest.mark.asyncio
async def test_rag_pipeline_memory_empty_triggers_chroma_fallback_and_restore(monkeypatch):
    # 极简配置，启用 Chroma，禁用其他外部依赖
    config = {
        'databases': {
            'chromadb': {'enabled': True},
            'mongodb': {'enabled': False},
            'neo4j': {'enabled': False},
        }
    }

    # 构造 DatabaseManager，但不进行真实初始化
    db_manager = DatabaseManager(config)

    # 准备 Fake Chroma 集合数据（无 embeddings，促发在 _restore_vectors_from_chromadb 中即时编码）
    fake_ids = ['vec_1', 'vec_2']
    fake_docs = ['这是第一段内容', '这是第二段内容']
    fake_metas = [
        {'document_id': 'doc_1', 'chunk_id': 'c1', 'model_name': 'stub', 'text_hash': 'h1', 'content': fake_docs[0]},
        {'document_id': 'doc_2', 'chunk_id': 'c2', 'model_name': 'stub', 'text_hash': 'h2', 'content': fake_docs[1]},
    ]
    fake_embeddings = [None, None]

    class FakeCollection:
        def get(self, include=None):
            assert include == ['embeddings', 'metadatas', 'documents']
            return {
                'ids': fake_ids,
                'embeddings': fake_embeddings,
                'metadatas': fake_metas,
                'documents': fake_docs,
            }

        def query(self, query_embeddings, n_results, where=None, where_document=None, include=None):
            # 返回与 ChromaDBClient.search_documents 兼容的结构
            return {
                'documents': [fake_docs[:1]],
                'metadatas': [fake_metas[:1]],
                'distances': [[0.2]],
                'ids': [[fake_ids[0]]],
            }

    class FakeChromaClient:
        def __init__(self):
            self._connected = True
            self.client = object()
            self._collection = FakeCollection()

        def get_collection(self, name: str):
            # KnowledgeProcessor 默认使用 'documents' 集合
            assert name in ('documents', 'conversations', 'memories') or name == 'documents'
            return self._collection

        async def search_documents(self, collection_name: str, query: str, n_results: int = 10, where=None, where_document=None):
            # 直接利用 FakeCollection.query 的返回
            res = self._collection.query(query_embeddings=[[0.0]], n_results=n_results, where=where, where_document=where_document, include=['documents','metadatas','distances'])
            return {
                'documents': res['documents'][0] if res['documents'] else [],
                'metadatas': res['metadatas'][0] if res['metadatas'] else [],
                'distances': res['distances'][0] if res['distances'] else [],
                'ids': res['ids'][0] if res['ids'] else [],
            }

    # 注入假 Chroma 客户端（通过私有属性，兼容只读 property）
    db_manager._chromadb_client = FakeChromaClient()

    # 构造 KnowledgeProcessor，并手动设置必要组件，避免真实初始化
    kp = KnowledgeProcessor(db_manager, config)
    kp.is_initialized = True
    kp.vectorizer = DocumentVectorizer(StubEmbeddingModel(), InMemoryVectorStore())

    # 触发检索
    query = '测试 Chroma 回退 检索 路径'
    results = await kp.search_knowledge(query=query, user_id=None, limit=5, threshold=0.7, use_rag_pipeline=True)

    # 断言：返回列表且包含回退来源的结果
    assert isinstance(results, list)
    has_chroma = any(r.get('retrieval_method') == 'chroma' for r in results)
    assert has_chroma, '应出现 ChromaDB 回退检索结果'

    # 断言：向量已从 Chroma 恢复到内存向量库
    assert hasattr(kp.vectorizer.vector_store, 'vectors')
    assert len(kp.vectorizer.vector_store.vectors) >= 1

    # 基本字段校验
    for item in [r for r in results if r.get('retrieval_method') == 'chroma']:
        assert 'content' in item
        assert 'similarity' in item
        assert 'vector_id' in item
        assert 'metadata' in item and isinstance(item['metadata'], dict)